package com.followerback.resources;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import twitter4j.ResponseList;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.User;
import twitter4j.auth.AccessToken;

import com.followerback.model.MatchedFollower;
import com.followerback.model.SearchPhrase;
import com.followerback.model.TwitterProfile;
import com.followerback.model.TwitterProfileFollower;
import com.followerback.model.TwitterProfileTwitterProfileFollower;
import com.google.appengine.api.channel.ChannelMessage;
import com.google.appengine.api.channel.ChannelService;
import com.google.appengine.api.channel.ChannelServiceFactory;
import com.google.appengine.api.search.Document;
import com.google.appengine.api.search.Field;
import com.google.appengine.api.search.Results;
import com.google.appengine.api.search.ScoredDocument;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyService;

@Path("/follower")
public class FollowerResource extends Resource {

	private static final Logger logger = Logger.getLogger(FollowerResource.class.getName());
	
	@Path("/save/{id}")
	@POST
	@Produces(MediaType.APPLICATION_JSON)
	public Response saveFollowers(@PathParam("id") @DefaultValue("-1") long id) {
		
		
		/* Initial stuff */
		logger.info("Entering saveFollowers");
		long methodStartTime = System.currentTimeMillis();
		if(id == -1) {
			String errorReason = "Missing required params";
			logger.severe(errorReason);
			return Response.ok().build();
		}
		Objectify objectify = ObjectifyService.begin();
		Twitter twitter = twitterFactory.getInstance();
		
		
		/* Get Access Token */
		// get twitter profile
		TwitterProfile twitterProfile = objectify.find(TwitterProfile.class, id);
		if(twitterProfile == null) {
			String errorReason = "Twitter profile not found";
			logger.severe(errorReason);
			return Response.ok().build();
		}
		
		// create access token
		AccessToken accessToken = new AccessToken(twitterProfile.getToken(), 
				twitterProfile.getTokenSecret());
		twitter.setOAuthAccessToken(accessToken);
		
		
		/* Save Connections */
		List<Long> followerIDs = getFollowerIDs(twitter, null);
		long twitterProfileID = twitterProfile.getId();
		for(Long followerID : followerIDs) {
			try {
				saveConnection(objectify, followerID, twitter, twitterProfileID);
			} catch (TwitterException e) {
				logger.severe(e.getErrorMessage());
				return Response.ok().build();
			}
		}
		
		/* End Stuff */
		logger.info("Exiting saveFollowers in " +(System.currentTimeMillis() - methodStartTime)/1000d+ " secs");
		return Response.ok().build();
	}
	
	@Path("/search/followers")
	@POST
	@Produces(MediaType.APPLICATION_JSON)
	public Response searchUsersFollowers(@FormParam("userID") @DefaultValue("-1") long userID, 
			@FormParam("token") @DefaultValue("") String token)  {
		
		
		/* Initial stuff */
		logger.info("Entering getUsersFollowers");
		long methodStartTime = System.currentTimeMillis();
		if(userID == -1 || token.isEmpty()) {
			String errorReason = "Missing required params";
			logger.severe(errorReason);
			return Response.serverError().build();
		}
		Objectify objectify = ObjectifyService.begin();
		Twitter twitter = twitterFactory.getInstance();
		ChannelService channelService = ChannelServiceFactory.getChannelService();
		

		/* Get Access Token */
		// get twitter profile
		TwitterProfile twitterProfile = objectify.find(TwitterProfile.class, userID);
		if(twitterProfile == null) {
			String errorReason = "Twitter profile not found";
			logger.severe(errorReason);
			return Response.ok().build();
		}
		
		// create access token
		AccessToken accessToken = new AccessToken(twitterProfile.getToken(), 
				twitterProfile.getTokenSecret());
		twitter.setOAuthAccessToken(accessToken);
		
		// get search phrases
		List<SearchPhrase> searchPhrases = objectify.query(SearchPhrase.class)
				.filter("twitterProfileID", userID).list();
		if(searchPhrases.isEmpty()) 
			return Response.ok().build();
		
		
		/* Process Next Follower */
		// get next follower to be processed
		Map<String, Long> twitterProfileFollowerIDCursorMap = getNextFollowerToBeProcessed(userID, 
				objectify);
		TwitterProfileFollower twitterProfileFollower = objectify.find(TwitterProfileFollower.class, 
				twitterProfileFollowerIDCursorMap.get("twitterProfileFollowerID"));
		
		// get follower's followers
		Map<String, Object> followerIDsCursorMap;
		try {
			followerIDsCursorMap = getFollowerIDsByHandle(twitter, 
					twitterProfileFollower.getScreenName(), twitterProfileFollowerIDCursorMap.get("cursor"));
		} catch (TwitterException e) {
			logger.severe(e.getErrorMessage());
			return Response.ok("Try again later").build();
		}
		@SuppressWarnings("unchecked")
		List<Long> followerIDs = (List<Long>)followerIDsCursorMap.get("followerIDs");
		
		// see if follower's followers already stored
		List<TwitterProfileFollower> alreadyStoredFollowers = new ArrayList<TwitterProfileFollower>();
		List<Long> followerIDsToBeRemoved = new ArrayList<Long>();
		List<MatchedFollower> matchedFollowers = new ArrayList<MatchedFollower>();
		for(Long followerID : followerIDs) {
			if(objectify.query(TwitterProfileFollower.class).filter("userID", followerID).count() > 0) {
				TwitterProfileFollower alreadyStoredFollower = objectify
						.query(TwitterProfileFollower.class).filter("userID", followerID).get();
				if(objectify.query(TwitterProfileTwitterProfileFollower.class)
						.filter("twitterProfileID", userID)
						.filter("twitterProfileFollowerID", alreadyStoredFollower.getId()).count() > 0) {
					followerIDsToBeRemoved.add(followerID);
				} else {
					alreadyStoredFollowers.add(alreadyStoredFollower);
				}
			}
		}
		
		// if any already stored followers, search to eliminate those possibilities
		if(!alreadyStoredFollowers.isEmpty()) {
			StringBuilder searchTermBuilder = new StringBuilder();
			for(SearchPhrase searchPhrase : searchPhrases) {
				if(searchTermBuilder.length() > 0)
					searchTermBuilder.append(" ");
				searchTermBuilder.append(searchPhrase.getText());
			}
			
			Results<ScoredDocument> results = getTwitterProfileFollowerIndex()
					.search(searchTermBuilder.toString());
			
			for (ScoredDocument document : results) {
		    	TwitterProfileFollower matchedTwitterProfileFollower = objectify
		    			.find(TwitterProfileFollower.class, Long.valueOf(document.getId()));
		    	if(matchedTwitterProfileFollower != null) {
		    		matchedFollowers.add(
		    				new MatchedFollower(token, matchedTwitterProfileFollower.getScreenName(), 
		    				matchedTwitterProfileFollower.getPictureURL(), 
		    				matchedTwitterProfileFollower.getDescription()));
		    		followerIDsToBeRemoved.add(matchedTwitterProfileFollower.getUserID());
		    	}
		    }
		}
		followerIDs.removeAll(followerIDsToBeRemoved);
		
		
		// search followers descriptions for matches
		int followerIDsSize = followerIDs.size();
		int followerIDsIndex = 0;
		int followerIDsIncrement = 100;
		while(followerIDsIncrement >= 100) {
			// make sure there's another 100 left to get in the array
			// if not adjust for it and this is last iteration of while loop
			if(followerIDsIndex + followerIDsIncrement >= followerIDsSize)
				followerIDsIncrement = followerIDsSize - followerIDsIndex - 1;
			
			// get twitter user
			ResponseList<User> responseList;
			try {
				responseList = twitter
						.lookupUsers(objectArrayToPrimitive(
								followerIDs.subList(followerIDsIndex, followerIDsIndex+followerIDsIncrement)));
			} catch (TwitterException e) {
				logger.severe(e.getErrorMessage());
				return Response.ok("Try again later").build();
			}
			
			// test each followers description for keywords
			for(User follower : responseList) {
				String followerDescription = follower.getDescription();
				if(followerDescription == null || followerDescription.isEmpty())
					continue;
				
				String lowerCaseFollowerDescription = followerDescription.toLowerCase();
				if(lowerCaseFollowerDescription.isEmpty())
					continue;
				
				// create profiles for matches
				for(SearchPhrase searchPhrase : searchPhrases) {
					if(lowerCaseFollowerDescription.contains(searchPhrase.getText())) {
						TwitterProfileFollower newTwitterProfileFollower = objectify
								.query(TwitterProfileFollower.class).filter("userID", follower.getId())
								.get();
						
						if(newTwitterProfileFollower == null) {
							newTwitterProfileFollower = new TwitterProfileFollower(follower.getId(), 
									follower.getScreenName(), follower.getBiggerProfileImageURL(), 
									follower.getDescription());
							
							objectify.put(newTwitterProfileFollower);
							postToIndexTwitterProfileIndex(newTwitterProfileFollower.getId());
						}
						
						matchedFollowers.add(
								new MatchedFollower(token, newTwitterProfileFollower.getScreenName(), 
										newTwitterProfileFollower.getPictureURL(), 
										newTwitterProfileFollower.getDescription()));
						break;
					}
				}
			}
			
			followerIDsIndex += followerIDsIncrement;
		}
		
		// save results
		objectify.put(matchedFollowers);
		
		// save cursor
		TwitterProfileTwitterProfileFollower twitterProfileTwitterProfileFollower = objectify
				.query(TwitterProfileTwitterProfileFollower.class).filter("twitterProfileID", userID)
				.filter("twitterProfileFollowerID", twitterProfileFollower.getId()).get();
		Object cursor = followerIDsCursorMap.get("cursor");
		if(cursor == null)
			cursor = -1L;
		twitterProfileTwitterProfileFollower.setCursor((Long)cursor);
		objectify.put(twitterProfileTwitterProfileFollower);
		
		// send results
		channelService.sendMessage(new ChannelMessage(token, gson.toJson(matchedFollowers)));
		
		
		/* End Stuff */
		logger.info("Exiting getUsersFollowers in " 
				+(System.currentTimeMillis() - methodStartTime)/1000d+ " secs");
		return Response.ok().build();
	}
	
	@Path("/index/{id}")
	@POST
	@Produces(MediaType.APPLICATION_JSON)
	public Response indexTwitterProfileFollower(@PathParam("id") @DefaultValue("-1") long followerID) {
	
	
		/* Initial stuff */
		logger.info("Entering indexTwitterProfileFollower");
		long methodStartTime = System.currentTimeMillis();
		if(followerID == -1) {
			String errorReason = "Follower ID missing";
			logger.severe(errorReason);
			Response.ok(errorReason).build();
		}
		Objectify objectify = ObjectifyService.begin();
		
		
		/* Get Chapter */
		// query chapter
		TwitterProfileFollower twitterProfileFollower = objectify
				.find(TwitterProfileFollower.class, followerID);
		if(twitterProfileFollower == null) {
			String errorReason = "Follower " +followerID+ " not found.";
			logger.severe(errorReason);
			Response.ok(errorReason).build();
		}
		
		// skip if no description
		String twitterProfileFollowerDescription = twitterProfileFollower.getDescription();
		if(twitterProfileFollowerDescription.isEmpty())
			return Response.ok().build();
		
		
		/* Index chapter */
		// make document
		Document document = Document.newBuilder().setId("" +followerID)
			.addField(Field.newBuilder().setName("description")
					.setText(twitterProfileFollowerDescription)).build();
	    
	    // put document
	    try {
	        // Put the document.
	    	getTwitterProfileFollowerIndex().put(document);
	    } catch (Exception e) {
	        logger.severe(e.getMessage());
	    }
	    
	    
	    /* End Stuff */
		logger.info("Exiting indexTwitterProfileFollower in " 
				+(System.currentTimeMillis() - methodStartTime)/1000d+ " secs");
		return Response.ok().build();
	}
}
